package com.i360day.invoker;

import com.i360day.invoker.context.HttpInvokerContext;
import com.i360day.invoker.exception.HttpInvokerException;
import com.i360day.invoker.hystrix.DefaultFallbackFactory;
import com.i360day.invoker.hystrix.DefaultHystrixMethodInterceptor;
import com.i360day.invoker.hystrix.FallbackFactory;
import com.i360day.invoker.proxy.TargetProxy;
import com.i360day.invoker.proxy.Targeter;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @program: spring-cloud-invoker-parent
 * @description:
 * @author: liju.z
 * @create: 2022-03-27 08:07
 **/
public class InvokerBuilder {

    private TargetProxy targetProxy;

    private Class<? extends FallbackFactory> fallbackFactory;

    private HttpInvokerContext httpInvokerContext;

    private String name;

    private String address;

    private String contextPath;

    private Targeter targeter;

    private MethodInterceptor interceptor;

    private Class<?> fallback;

    private InvokerBuilder() {
    }

    public static InvokerBuilder create() {
        return new InvokerBuilder();
    }

    public InvokerBuilder contextPath(String contextPath) {
        this.contextPath = contextPath;
        return this;
    }

    public InvokerBuilder address(String address) {
        this.address = address;
        return this;
    }

    public InvokerBuilder name(String name) {
        this.name = name;
        return this;
    }

    public InvokerBuilder targetProxy(TargetProxy targetProxy) {
        this.targetProxy = targetProxy;
        return this;
    }

    public InvokerBuilder targeter(Targeter targeter) {
        Assert.notNull(targeter, "targeter Not found in spring context");
        this.targeter = targeter;
        return this;
    }

    public InvokerBuilder interceptor(MethodInterceptor interceptor) {
        this.interceptor = interceptor;
        return this;
    }

    public InvokerBuilder fallbackFactory(Class<? extends FallbackFactory> fallbackFactory) {
        this.fallbackFactory = fallbackFactory;
        return this;
    }

    public InvokerBuilder fallback(Class<?> fallback) {
        this.fallback = fallback;
        return this;
    }

    public InvokerBuilder httpInvokerContext(HttpInvokerContext httpInvokerContext) {
        this.httpInvokerContext = httpInvokerContext;
        return this;
    }

    public MethodInterceptor build() {
        Assert.notNull(httpInvokerContext, "httpInvokerContext is null");
        return targeter.target(toDispatchMap(interceptor, getTargetProxy()), createFallbackFactory(), getTargetProxy());
    }

    /**
     * 将当前类的所有方法存储
     *
     * @param interceptor
     * @param target
     * @param <T>
     * @return
     */
    private <T> Map<Method, MethodInterceptor> toDispatchMap(MethodInterceptor interceptor, TargetProxy<T> target) {
        Map<Method, MethodInterceptor> dispatchMethodMap = new LinkedHashMap<>();
        Method[] methods = target.getType().getMethods();
        for (Method method : methods) {
            dispatchMethodMap.put(method, interceptor);
        }
        return dispatchMethodMap;
    }

    /**
     * 创建降级工厂
     *
     * @return
     */
    private FallbackFactory createFallbackFactory() {
        //获取 FallbackFactory
        if (!getFallback().equals(void.class)) {
            return new DefaultHystrixMethodInterceptor(getFallback(), httpInvokerContext);
        } else if (!getFallbackFactory().equals(DefaultFallbackFactory.class)) {
            FallbackFactory fallbackFactory = httpInvokerContext.getBean(getFallbackFactory());
            //如果获取不到，重新实例化一个
            if (fallbackFactory == null) {
                Assert.isTrue(!getFallbackFactory().isInterface(), getFallbackFactory().getName() + " can only be specified on an service");
                try {
                    return getFallbackFactory().getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    throw new HttpInvokerException(e.getMessage());
                }
            }
            return fallbackFactory;
        }
        return new DefaultFallbackFactory();
    }

    public TargetProxy getTargetProxy() {
        return targetProxy;
    }

    public Class<? extends FallbackFactory> getFallbackFactory() {
        return fallbackFactory;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    public String getContextPath() {
        return contextPath;
    }

    public Targeter getTargeter() {
        return targeter;
    }

    public MethodInterceptor getInterceptor() {
        return interceptor;
    }

    public HttpInvokerContext getHttpInvokerContext() {
        return httpInvokerContext;
    }

    public Class<?> getFallback() {
        return fallback;
    }
}
